package com.xored.glance.internal.ui.search;

import java.util.regex.Matcher;

import com.xored.glance.internal.ui.search.SearchJob.ISearchMonitor;
import com.xored.glance.ui.sources.ITextBlock;
import com.xored.glance.ui.sources.ITextSource;
import com.xored.glance.ui.sources.Match;

/**
 * @author Yuri Strot
 * 
 */
public class SearchEngine extends Thread {

	public SearchEngine(ISearchListener listener) {
		this.listener = listener;
	}

	public void setSource(SearchRule rule, ITextSource source, boolean paused) {
		synchronized (monitor) {
			if (scope != null) {
				scope.dispose();
				scope = null;
			}
			scope = new SearchScope(source);
			this.paused = paused;
			doSetRule(rule);
		}
	}

	public void selectNext() {
		if (scope != null) {
			scope.selectNext();
		}
	}

	public void selectPrev() {
		if (scope != null) {
			scope.selectPrev();
		}
	}

	public void run() {
		while (!exit) {
			sleepWhileInterrupted(0);
			if (scope == null)
				continue;
			if (exit)
				break;
			while (true) {
				long start = System.currentTimeMillis();
				if (!findMatches())
					continue;
				long waitPause = paused ? PAUSE
						- (System.currentTimeMillis() - start) : 0;
				if (waitPause > 0)
					sleepWhileInterrupted(waitPause);
				if (!scope.isCanceled())
					break;
			}
			listener.finished();
			scope.showMatches();
		}
	}

	public void setRule(SearchRule rule) {
		synchronized (monitor) {
			doSetRule(rule);
		}
	}

	protected void doSetRule(SearchRule rule) {
		cancel = true;
		boolean findFirst = false;
		if (this.rule == null || !this.rule.equals(rule)) {
			this.rule = rule;
			findFirst = true;
			matcher = rule.getText().length() == 0 ? null : rule.getPattern()
					.matcher(new String());
		}
		if (scope != null) {
			scope.updateMatcher(findFirst);
		}
		interrupt();
	}

	public void exit() {
		synchronized (monitor) {
			exit = true;
			interrupt();
		}
	}

	private boolean findMatches() {
		cancel = false;
		if (matcher == null) {
			scope.showEmptyText();
			return true;
		}
		SearchJob job = null;
		while ((job = scope.getJob()) != null) {
			if (!job.run())
				return false;
		}
		scope.updateResult();
		return true;
	}

	private void sleepWhileInterrupted(long millis) {
		try {
			if (millis == 0) {
				while (true)
					Thread.sleep(50);
			} else
				Thread.sleep(millis);
		} catch (InterruptedException e1) {
		}
	}

	private class SearchScope extends AbstractSearchScope implements
			ISearchMonitor {

		private boolean firstFound = false;

		/**
		 * @param source
		 */
		public SearchScope(ITextSource source) {
			super(source);
		}

		public SearchJob getJob() {
			synchronized (monitor) {
				while (currentEntry < entries.size()) {
					SearchJob job = (SearchJob) entries.get(currentEntry);
					if (!job.isFinished())
						return job;
					currentEntry++;
				}
				return updateEntry();
			}
		}

		public void updateResult() {
			if (!firstFound) {
				listener.firstFound(null);
				firstFound = true;
			}
			listener.allFound(getMatches());
		}

		@Override
		public void added(SearchScopeEntry entry, Match match) {
			if (!firstFound) {
				select(match);
				listener.firstFound(match);
				firstFound = true;
			}
		}

		@Override
		public void cleared(SearchScopeEntry entry) {
			synchronized (monitor) {
				cancel = true;
				interrupt();
			}
		}

		public boolean isCanceled() {
			synchronized (monitor) {
				return cancel;
			}
		}

		@Override
		public void blocksChanged(ITextBlock[] removed, ITextBlock[] added) {
			synchronized (monitor) {
				super.blocksChanged(removed, added);
				cancel = true;
				interrupt();
			}
		}

		@Override
		public Match[] getMatches() {
			synchronized (monitor) {
				return super.getMatches();
			}
		}

		@Override
		protected SearchScopeEntry createEntry(ITextBlock block) {
			return new SearchJob(block, matcher, this);
		}

		protected void updateMatcher(boolean findFirst) {
			firstFound = !findFirst;
			for (SearchScopeEntry entry : entries) {
				SearchJob job = (SearchJob) entry;
				job.update(matcher);
			}
			updateStart();
		}

		private SearchJob updateEntry() {
			currentEntry = 0;
			for (SearchScopeEntry entry : entries) {
				SearchJob job = (SearchJob) entry;
				if (!job.isFinished())
					return job;
				currentEntry++;
			}
			currentEntry = 0;
			return null;
		}
	}

	private static final long PAUSE = 100 * 5;// 100 s

	private boolean cancel;
	private SearchRule rule;
	private Matcher matcher;
	private Object monitor = new Object();
	private boolean exit;
	private boolean paused;

	private SearchScope scope;
	private ISearchListener listener;

}
